home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The 640 MEG Shareware Studio 2
/
The 640 Meg Shareware Studio CD-ROM Volume II (Data Express)(1993).ISO
/
basic
/
bastips2.zip
/
BASICMEM.TXT
< prev
next >
Wrap
Text File
|
1986-07-02
|
18KB
|
355 lines
Extra Memory for BASIC
(PC Magazine Vol 5 No 8 Apr 29, 1986 PC Tutor)
A programmer is trying to develop a multipage screen-buffer
technique for a monochrome display that will provide a function
similar to the color adapter's ability to keep several different
screen images stored in memory and flip them to the screen when
appropriate. Using an assembler routine, he is unable to use the
highest 12K when running with the BASIC interpreter. However, when
the same program is run in compiled form (with appropriate changes
such as CALL ABSOLUTE), the machine goes beserk. Is there a way to
do this using the memory-management DOS function calls (48h, 49h,
and 4Ah) in DOS 2.1?
Editor's Response:
Programs compiled under the old IBM and Microsoft BASIC compilers
use all available memory from the beginning of the program upward.
Since these compilers were designed to work under DOS 1.1, they do not
free up any of this program memory using function call 4Ah. Conse-
quently, a function call 48h from within an assembly language routine
will not allocate a memory block since there is no memory left to
allocate. Nor could you first deallocate some of the compiled BASIC's
memory with an assembler subroutine using function call 4Ah, because
the program has already set itself up in the memory and expects it all
to be usable. There are a couple of solutions to the problem, however.
Under the BASIC compiler you could define a string variable 4000
bytes long to hold the monochrome display contents with:
MONOHOLD$ = STRING$(4000,0)
You could then use VARPTR to pass the address of this string to your
assembly language subroutine. (Note that the first 2 bytes pointed
to with VARPTR(MONOHOLD$) contain the length of the string, which is
4000.) However, this would not work under the BASIC interpreter,
whose strings are limited to 255 bytes.
If you'd like to keep the compiled and interpreted BASIC programs
consistent in the way they store data in upper memory, therefore, you
need to prevent the compiled BASIC program from taking up all of
available memory. This can be done by modifying some of the header
information in the compiled .EXE run file, though, as you'll see there
may be some problems in this approach.
Every .EXE file begins with header information DOS needs to load
the program into memory, perform segment fix-ups, and allocate space
for it to run. This header information is documented in the DOS
manuals for versions 2.0 and earlier and in the DOS Technical Reference
manuals for versions 2.1 and later.
You can't see this header information if you load the .EXE file
directly into DEBUG, because DEBUG uses the header to perform all the
space allocation and fix-ups, making the program all ready to run.
You'll have to first rename the file to an extension other than .EXE
and then load it into DEBUG. (But then you won't be able to run it
in DEBUG.)
Use the following commands to look at the first part of an .EXE
file header:
RENAME runfile.EXE runfile.XXX
DEBUG
-N runfile.XXX
-L 0
-D 0
The 2-byte word beginning at offset 000Ah is defined in the DOS
Technical Reference manual as the "minimum number of 16-byte paragraphs
required above the end of the loaded program." This word will often
be at offset 0000h. The 2-byte word at offset 000Ch is the "maximum
number of 16-byte paragraphs required above the end of the loaded
program." This will usually be FFFFh, which means that the program
wants all available memory above where it loads.
This extra memory space is used for the "heap" and the "stack."
During calculations, for instance, the stack is used to store inter-
mediate results. The heap is used by the program mostly for dynamic
storage. If your program executes a STRING$ command such as shown
above, or if it DIMensions an array, the result has to go somewhere.
It goes in the heap. In a program that does a lot of dynamic string
and array allocation, the heap can get pretty cluttered up and
disorganized. At times, normal execution can grind to a halt while
the program cleans up the heap in a process technically referred to
as "garbage collection."
Of course, most compiled BASIC programs don't really need all
available memory and can function normally with less. Thus, you can
change the "maximum memory" word at offset 000Ch in the .EXE header
to something lower than FFFFh to prevent DOS from allocating all
available memory to the program.
Let's assume, for example, that you have 256K memory in your
machine and that the bottom 128K memory is taken up by DOS and
resident programs. (You get this second figure by subtracting the
"bytes free" from the "bytes total memory" information provided by
CHKDSK.) If your .EXE program is about 32K, you can give it another
64K when it runs and still have 32K left over at the top of memory
for your screen swaps.
To do this, you'd enter the following DEBUG commands as a direct
continuation of the program above (whose last line was: -D 0):
-E 000C 00 10
-W
-Q
RENAME runfile.XXX runfile.EXE
Note that the 2 bytes (00 and 10) specified after the E (Enter) command
are in reverse order, as usual. The actual word you've entered is
1000h or 4096 decimal. This is the number of 16-byte paragraphs, and
it corresponds to 64K bytes.
When you now run your .EXE program, the DOS loaders uses this
"maximum memory" word in the .EXE header to computer a "top of memory"
word (also in paragraph form) that it puts in offset 0002h of the
programs Program Segment Prefix. The compiled BASIC prologue uses
this value to limit the amount of memory it uses.
You can look at the Program Segment Prefix by loading the .EXE
file (not the renamed .XXX file) into DEBUG and executing the command:
-D 0
If an .EXE header has FFFFh in offset 000Ch, the Program Segment Prefix
top of memory word at 0002h will be 4000h for a 256K machine and A000h
for a 640K machine (again, you'll see the 2 bytes shown in reverse
order). If the amount of real available memory exceeds the length of
the program plus the additional memory (plus the memory taken up by
DEBUG), then you'll see a lower value. If you have enough high memory
for screen swaps while in DEBUG, you'll certainly have enough when
running the program outside of DEBUG.
How low can you set the value at 000Ch in the .EXE header?
Definitely don't set it to 0 -- that indicates to DOS that the program
is to be loaded high in memory, and it will crash. With some compiled
BASIC programs, it can be set as low as 0060h paragraphs (about 1.5K
bytes). If you have a lot of dynamic string and array storage in your
program, you'll need something higher, or you'll probably get an "out
of memory" error during program execution.
Note also that the program may run slower, since it has to perform
more frequent garbage collections to clean up the heap. You'll probably
have to experiment to get an optimum value.
If you do your compilation and linking using a batch file with a
replaceable parameter for the filename, you can automate this .EXE
header modification with the following batch file code after the LINK:
RENAME %1.EXE %1.XXX
DEBUG %1.XXX < HEADCHG
RENAME %1.XXX %2.EXE
where HEADCHG is a file containing the DEBUG commands:
E 010C 00 10
W
Q
Note here that you have to use 0100h plus the header offset for the
E (Enter) command because DEBUG is loading the file at offset 0100h.
The .EXE file header also contains a word "checksum" value, which
is supposed to be used by the DOS loader to ensure .EXE file integrity.
If we change a value in the header, then the checksum will not check.
However, DOS doesn't use this checksum when loading programs. If it
does in the future, you'll have to make an adjustment to that value as
well.
You may also be interested to learn that the LINK program included
with the Microsoft (but not the IBM) Macro Assembler and the Microsoft
C Compiler includes a switch called CPARMAXALLOC to set the maximum
memory value during the LINK so you don't have to adjust it later.
The next version of the Microsoft C Compiler also has an EXEMOD program
to change this header information with a one-line command.
(Persons tired of having COMMAND.COM reload after running a
compiled BASIC program may alter this word to prevent the program from
overwriting the transient part of COMMAND.COM at high memory.)
Although there's nothing wrong with modifying this part of the .EXE
header, you may have problems using memory above the loaded program.
First, the value you pick to go in the .EXE header will probably
be dependent on your machine's memory configuration. If it's run on a
machine with a few more resident programs loaded, you may not have
enough high memory to store you screen swaps.
Second, in a multitasking system such as TopView, this solution
will be absolutely disastrous, because your program is using memory
that doesn't belong to it. Only memory up to that top of memory word
in the Program Segment Prefix is allocated for your program, and you
are deliberately using memory higher than that. TopView may be using
this memory for some other program. The same problem exists if you
use high memory in the BASICA interpreter.
You also don't want to write over the first few bytes right after
the top of the extra memory, because these contain a marker DOS uses
for memory allocation.
There is another solution, but it would require using a separate
assembly language program to allocate memory below your loaded program.
This assembly language program would perform the following four steps:
1) deallocate all memory above its code by executing the DOS 4Ah
function call;
2) allocate a block of memory for screen swaps (or BLOADs or
whatever) with function call 48h;
3) store the address of this block in lower memory, probably in
one of the user interrupt vectors beginning at 0000:0180h; and,
4) load the compiled BASIC program (or the BASICA interpreter)
using the EXEC function call 4Bh.
The BASIC program -- in either interpreted or compiled form --
could retrieve the address of this allocated memory block using PEEK
and pass this address to the assembly language subroutine. When the
BASIC program terminates, control would pass back to the assembler
program that loaded it, which would then terminate.
-----------------------------------------------------------------
Minding Memory From BASIC
(COMPUTE! Magazine June 1986 by D. W. Neuendorf)
Memory management in DOS has become an important issue. The new
desktop tools and coresident programs are designed to wait in the
background to be called during the operation of another program. A
number of these utilities may be lurking in memory at once, and
programmers can't predict which other programs will be present with
their own. The result can be memory conflicts and system crashes.
DOS 2.0 and later versions contain several function calls designed
to give the operating system control over how the computer's memory is
divided among programs residing in memory simultaneously. The most
basic of these functions simply attempt to allocate and deallocate
blocks of memory at a program's request. These DOS calls are readily
available to machine language programmers, just like all other machine-
level resources.
BASIC programmers, on the other hand, have no direct access to
many DOS functions. But there are ways for BASIC programs to call on
DOS to perform these memory management tasks.
There are two DOS functions we're interested in -- one for
allocating memory and another for deallocating memory.
In machine language, both functions are called by placing a
function number in the microprocessor's AH register and calling
Interrupt 21h. (Function numbers indicated to DOS which function is
being called. The interrupt then performs the function.) The numbers
are 48h for the allocate function and 49h for the deallocate function.
In addition to these numbers, each function call requires that
you pass an argument. The allocate function requires the number of
16-byte paragraphs of memory to be allocated. This number must be
placed in the microprocessor's BX register. The deallocate function
requires the segment address of a block to be deallocated. This
number must be placed in the ES register.
After each function is performed, it returns a value. The
allocate function returns, via the AX register, either the segment
address of an allocated block or an error code (7 or 8 plus a set
carry bit) if the function was unsuccessful. The deallocation routine
returns nothing if successful, but sets the carry bit and returns an
error code (7 or 9) if unsuccessful. The machine language listings
below show the assembler code necessary to call these functions.
The BASIC program shows how to call these functions from BASIC.
Since the allocate routine is not available initially and therefore
can't allocate space for itself, the program reserves a few bytes for
it just above BASIC (using the CLEAR statement in line 10). Once the
allocate routine has been installed (lines 40-60), it can be used to
get memory from DOS for machine language routines and other data. An
example of its use is the call in line 70, which gets the segment
address of a memory block for the deallocate routine. Finally, line
120 shows an example of using the deallocate routine -- it deallocates
its own memory.
After studying the BASIC program, you'll see that it's possible
to put a machine language subroutine outside BASIC's 64K memory area,
thus saving some space for BASIC programs. Better yet, you don't have
to worry about where in memory you're hiding the routine -- DOS takes
care of it. If you use a lot of machine language subroutines or
store large amounts of data in memory, you'll have a lot more room to
work with if you don't have to put everything inside BASIC's own
segment.
If everyone relies on DOS to determine where their programs reside
in memory, we can all feel confident that our coresident programs are
not overlapping and conflicting with each other. But if too many
programmers bypass these DOS functions, the rest of us won't dare to
rely on them, either. After all, DOS can protect only the data or
programs that it knows about.
DOS Memory Allocation:
page 50,132
0000 alloc segment para
assume cs:alloc
assume ds:alloc
assume es:alloc
0000 allocate proc far
;
;Routine to allow BASIC to make DOS call to allocate
;a block of memory outside of BASIC's own segment.
;CALL ALLOC(MEMORY) - when BASIC calls the routine,
;MEMORY contains the number of bytes to be allocated.
;When the routine returns to BASIC, MEMORY contains
;the segment address of the allocated block of memory.
;A 7 or 8 indicates allocation failed.
;
0000 55 push bp
0001 8B EC mov bp,sp
0003 8B 5E 06 mov bx,[bp+6] ;get address of MEMORY
0006 8B 1F mov bx,[bx] ;get number of bytes to be allocated
0008 B4 48 mov ah,48h ;DOS function number
000A CD 21 int 21h ;DOS call itself
000C 8B 5E 06 mov bx,[bp+6] ;address of MEMORY
000F 89 07 mov [bx],ax ;put segment address of allocated
memory in MEMORY
0011 5D pop bp
0012 CA 0002 ret 2
;
0015 allocate endp
0015 alloc ends
end
DOS Memory Deallocation:
page 50,132
0000 dealloc segment para
assume cs:dealloc
assume ds:dealloc
assume es:dealloc
0000 dlc proc far
;
;Routine to allow BASIC to make DOS call to deallocate
;a block of memory previously allocated using ALLOC.
;CALL DEALLOC(MEMORY) - when BASIC calls the routine,
;MEMORY contains the segment address of the block of
;memory to be dealloc. When the routine returns to
;BASIC, MEMORY contains either the original segment
;address or an error code. A 7 or 9 indicates
;allocation failed.
;
0000 55 push bp
0001 06 push es
0002 8B EC mov bp,sp
0004 8B 5E 06 mov bx,[bp+8] ;get address of MEMORY
0007 8E 07 mov es,[bx] ;get segment address of block to be
deallocated
0009 B4 49 mov ah,49h ;DOS function number
000B CD 21 int 21h ;DOS call itself
000D 8B 5E 06 mov bx,[bp+8]
0010 89 07 mov [bx],ax ;put error code in MEMORY
0012 07 pop es
0013 5D pop bp
0014 CA 0002 ret 2
;
0017 dlc endp
0017 dealloc ends
end
DOS Memory Functions in BASIC:
10 CLEAR ,&HFFDF 'Reserve a few bytes just above BASIC for alloc routine
20 DEFINT A-Z
30 DEF SEG:ALLOC=&HFFDF:DMEMORY=2:DEALLOC=0
40 RESTORE 50:FOR X=0 TO 20:READ Y:POKE X+ALLOC,Y:NEXT 'Install alloc.
50 DATA &h55,&h8b,&hec,&h8b,&h5e,&h06,&h8b,&h1f,&hb4,&h48,&hcd
60 DATA &h21,&h8b,&h5e,&h06,&h89,&h07,&h5d,&hca,&h02,&h0
70 CALL ALLOC(DMEMORY) 'DOS call to allocate memory for dealloc routine
80 DEF SEG=DMEMORY
90 RESTORE 100:FOR X=0 TO 22:READ Y:POKE X,Y:NEXT 'Install dealloc
100 DATA &h55,&h06,&h8b,&hec,&h8b,&h5e,&h08,&h8e,&h07,&hb4,&h49,&hcd
110 DATA &h21,&h8b,&h5e,&h08,&h89,&h07,&h07,&h5d,&hca,&h02,&h00
120 CALL DEALLOC(DMEMORY)
130 END